package cn.clj;

import cn.clj.cell.CljCell;
import cn.clj.cell.CljCellType;

public class CljLexer {
    private CljCharIterator myCharIterator;
    private CljCell token;

    public CljLexer(CljCharIterator myCharIterator) {
        this.myCharIterator = myCharIterator;
    }

    /**
     * 当前 token
     * */
    public CljCell current()
    {
        return token;
    }

    /**
     * 下一个
     * */
    public boolean next() throws Exception {
        try {
            this.token = nextToken();
            return true;
        } catch (Exception ex) {
            if (ex.getMessage().equals("unexpected EOF")) {
                this.token = null;
                return false;
            }
            else
            {
                throw ex;
            }
        }
    }

    /**
     * 下一个 token
     * */
    public CljCell nextToken() throws Exception {
        char c;
        /**
         * 剔除空格和注释
         * */
        for (;;) {
            /**
             * 如果是空格就继续移动到非空格
             * */
            while (Character.isWhitespace(this.myCharIterator.current())) {
                this.myCharIterator.next();
            }

            /**
             * 剔除 lisp 中的注释
             * 在 lisp 中注释是以 ; 开头， \n 结尾的
             * */
            c = this.myCharIterator.current();
            if (c == ';') {
                this.myCharIterator.next();
                while (this.myCharIterator.current() != '\n') {
                    this.myCharIterator.next();
                }
            }
            else {
                break;
            }
        }

        switch (c) {
            case '~':
                this.myCharIterator.next();
                if (this.myCharIterator.current() == '@')
                {
                    this.myCharIterator.next();
                    return new CljCell(CljCellType.unquote_list);
                }
                return new CljCell(CljCellType.unquote);
            case '>':
                this.myCharIterator.next();
                if (this.myCharIterator.current() == '=')
                {
                    this.myCharIterator.next();
                    return new CljCell(CljCellType.greater_equals_clj);
                }
                return new CljCell(CljCellType.greater_than_clj);
            case '<':
                this.myCharIterator.next();
                if (this.myCharIterator.current() == '=')
                {
                    this.myCharIterator.next();
                    return new CljCell(CljCellType.less_equals_clj);
                }
                return new CljCell(CljCellType.less_than_clj);
            case '(':
                this.myCharIterator.next();
                return new CljCell(CljCellType.lparen);
            case ')':
                this.myCharIterator.next();
                return new CljCell(CljCellType.rparen);
            case '\'':
                this.myCharIterator.next();
                return new CljCell(CljCellType.quote);
            case '`':
                this.myCharIterator.next();
                return new CljCell(CljCellType.quote_list);
            case '[':
                this.myCharIterator.next();
                return new CljCell(CljCellType.lbracket);
            case ']':
                this.myCharIterator.next();
                return new CljCell(CljCellType.rbracket);
            case '{':
                this.myCharIterator.next();
                return new CljCell(CljCellType.lbraces);
            case '}':
                this.myCharIterator.next();
                return new CljCell(CljCellType.rbraces);
            case '"':
                String s = CljUtil.getString(this.myCharIterator);
                this.myCharIterator.next();
                return new CljCell(CljCellType.string_clj, s);
            case '&':
                this.myCharIterator.next();
                return new CljCell(CljCellType.and_clj);
            case '+':
                this.myCharIterator.next();
                return new CljCell(CljCellType.addition);
            case '-':
                this.myCharIterator.next();
                return new CljCell(CljCellType.subtraction);
            case '*':
                this.myCharIterator.next();
                return new CljCell(CljCellType.multiplication);
            case '/':
                this.myCharIterator.next();
                return new CljCell(CljCellType.division);
            default:
                StringBuilder sb = new StringBuilder();
                for (;;)
                {
                    sb.append(c);
                    if (!this.myCharIterator.next())
                        break;

                    c = this.myCharIterator.current();
                    if (c == '(' || c == ')' || c == '\'' || c == '[' || c == ']' || c == '"' || c == '~' || Character.isWhitespace(c))
                        break;
                }
                String tk = sb.toString();
                if ("nil".equals(tk))
                {
                    return new CljCell(CljCellType.nil);
                }
                else
                {
                    Number num = CljUtil.tryToParseAsNumber(tk);
                    if (num != null)
                    {
                        return new CljCell(CljCellType.number_clj, num);
                    }
                    else if (tk.startsWith(":")) // ：以关键字开头？
                    {
                        String tail = tk.substring(1);
                        if (CljUtil.checkSymbol(tail))
                        {
                            if (tail.equals("else")) {
                                return new CljCell(CljCellType.else_clj);
                            } else {
                                return new CljCell(CljCellType.symbol, tk);
                            }

//                            try {
//                                return new CljCell(CljCellType.My_Keyword, tk);
//                            } catch (IllegalArgumentException ex) {
//                                return new CljCell(CljCellType.symbol, tk);
//                            }
                        }
                    }

                    switch (tk) {
                        case "defmacro":
                            return new CljCell(CljCellType.defmacro);
                        case "defn":
                            return new CljCell(CljCellType.defn);
                        case "def":
                            return new CljCell(CljCellType.def);
                        case "let":
                            return new CljCell(CljCellType.let);
                        case "cond":
                            return new CljCell(CljCellType.cond);
                        case "if":
                            return new CljCell(CljCellType.if_clj);
                        case "first":
                            return new CljCell(CljCellType.first);
                        case "last":
                            return new CljCell(CljCellType.last);
                        case "rest":
                            return new CljCell(CljCellType.rest);
                        case "do":
                            return new CljCell(CljCellType.do_clj);
                        case "loop":
                            return new CljCell(CljCellType.loop);
                        case "recur":
                            return new CljCell(CljCellType.recur);
                        case "apply":
                            return new CljCell(CljCellType.apply);
                        case "map":
                            return new CljCell(CljCellType.map);
                        case "cons":
                            return new CljCell(CljCellType.cons);
                        case "reduce":
                            return new CljCell(CljCellType.reduce);
                        case "eval":
                            return new CljCell(CljCellType.eval);
                        case "true":
                            return new CljCell(CljCellType.boolean_clj, true);
                        case "false":
                            return new CljCell(CljCellType.boolean_clj, false);
                        case "string_func":
                            return new CljCell(CljCellType.string_func);
                        case "number_func":
                            return new CljCell(CljCellType.number_func);
                        case "boolean_func":
                            return new CljCell(CljCellType.boolean_func);
                        case "date_func":
                            return new CljCell(CljCellType.date_func);
                        case "int_func":
                            return new CljCell(CljCellType.int_func);
                        case "long_func":
                            return new CljCell(CljCellType.long_func);
                        case "char_func":
                            return new CljCell(CljCellType.char_func);
                        case "short_func":
                            return new CljCell(CljCellType.short_func);
                        case "byte_func":
                            return new CljCell(CljCellType.byte_func);
                        case "double_func":
                            return new CljCell(CljCellType.double_func);
                        case "float_func":
                            return new CljCell(CljCellType.float_func);

                        case "not":
                            return new CljCell(CljCellType.not);
                        case "nil?":
                            return new CljCell(CljCellType.is_nil);
                        case "and":
                            return new CljCell(CljCellType.and);
                        case "or":
                            return new CljCell(CljCellType.or);
                        case ":else":
                            return new CljCell(CljCellType.else_clj);
                        case "println":
                            return new CljCell(CljCellType.println);
                    }

                    if (CljUtil.checkSymbol(tk)) {
                        return new CljCell(CljCellType.symbol, tk);
                    }
                }
                throw new Exception ("bad token: " + CljUtil.toStr(tk));
        }
    }

    /**
     * 关闭输入流
     * */
    public void close() {
        this.myCharIterator.myLinesFromString.close();
    }

    /**
     * 当前行号
     * */
    public int getLineNumber()
    {
        return this.myCharIterator.lineNumber;
    }

    /**
     * 当前行 index
     * */
    public int getLineIndex()
    {
        return this.myCharIterator.i;
    }

    /**
     * 当前行
     * */
    public String getLine()
    {
        return this.myCharIterator.line;
    }
}
